Angular 提供了一種可以很擴充元件或HTML標籤屬性的方式,叫做 directive
,透過這種方式我們可以很輕易地替現有的元件或 HTML 擴充它的顯示或行為,但單純擴充有時候是不夠的,如果我們希望能夠在程式中操作 directive 的話,今天學習的 exportAs
就是一個很好的使用時機。
類型:技巧
難度:3 顆星
實用度:4 顆星
假設我們設計了一個簡單的 directive 如下:
@Directive({
selector: '[appColorful]'
})
export class ColorfulDirective {
@Input() appColorful;
@HostBinding('style.color') get color() {
console.log(this.appColorful);
return this.appColorful || 'red';
}
}
這個 directive 很簡單,就是繫結宿主元素(host element)的 color
樣式而已,在使用時也很容易:
<p appColorful="blue">Hello World</p>
這本身是非常容易的一件事情,現在我們把 directive
稍微複雜化一點,變成下面程式:
@Directive({
selector: '[appColorful]'
})
export class ColorfulDirective {
@Input() appColorful;
@HostBinding('style.color') get color() {
console.log(this.appColorful);
return this.appColorful || 'red';
}
changeColor(color) {
this.appColorful = color;
}
}
跟原來的程式相比,其實只是加了 changeColor
方法而已,我們希望可以在 TypeScript 裡面不透過屬性繫結的方式,來讓 directive
呼叫 changeColor()
方法改變顏色,接下來的問題是,如何取得這個 `directive` 實體呢?
在設計 component 時,我們只需要掛上樣板參考變數,就能夠取得 component 的實體,例如:
<!-- 此時的 colorful 就是 ColorfulComponent 本身 -->
<app-colorful #colorful></app-colorful>
若是在樣板中的 component 在掛上 directive 時,很明顯的樣板參考變數也無法取得 directive 本身的實體,若是掛在單純的 HTML 標籤上呢?我們可以試試看會取得什麼東西?
@Component({
selector: 'my-app',
template: `
<p appColorful="blue" #color>Hello World</p>
<button (click)="change()">Change Color</button>
`,
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
@ViewChild('color') color;
change() {
console.log(this.color);
}
}
在按下按鈕呼叫 change()
時,把這個變數印出來看看,如下:
可以看到我們單純的是取得 ElementRef
代表這個宿主元素的主體,而非 directive 本身。那麼到底要怎麼拿到 directive 實體呢?就是今天的重點 exportAs
啦!
在宣告 @Directive()
裝飾我們的 directive 時,裡面可以設定一個 exportAs: string
,用來代表這個 directive 實體要以什麼名稱分享出去,所以我們的 @Directive()
可以調整成如下:
@Directive({
selector: '[appColorful]',
// 加上 exportAs
exportAs: 'colorful'
})
之後在樣板上就能夠改用 #color="colorful"
的方式,正確的取得 directive 實體啦!取得這個實體後,就可以隨意呼叫裡面的方法:
@Component({
selector: 'my-app',
template: `
<!-- 使用 #color="colorful" 取得實體 -->
<p appColorful="blue" #color="colorful">Hello World</p>
<button (click)="change()">Change Color</button>
`,
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
// 確定取得的是 ColorDirective 的實體
@ViewChild('color') color: ColorfulDirective;
change() {
console.log(this.color);
this.color.changeColor('black');
}
}
透過這種方式,我們在設計 directive 時,就可以透過 exportAs
來設定給外部程式使用的實體,就能夠在程式中針對 directive 進行更細緻的處理,整體設計時也會更加有彈性囉!
在設計
exportAs
時,為了避免名稱衝突,通常都會直接設定成跟selector
設定一樣的名稱。
當我們使用 Template driven form 進行表單驗證時,會出現像是 #name="ngModel"
這樣的語法:
<input id="name" name="name" [(ngModel)]="hero.name" #name="ngModel" >
其實就是 ngModel
這個 directive 也加上了 exportAs: []
,原始碼看起來如下:
@Directive({
selector: '[ngModel]:not([formControlName]):not([formControl])',
providers: [formControlBinding],
exportAs: 'ngModel'
})
這樣是不是對 Template driven form 表單驗證常用技巧的原理更加熟悉一點啦!?
今天我們介紹的 exportAs
,在一般應用程式中使用的頻率通常比較低,但使用 exportAs
也是有很多好處的,最明顯的是我們可以減少很多不必要的 @Input
等設計,直接取得 directive 實體並呼叫相關方法即可;同樣的我們也可以減少樣板上不必要的設定,把大部分的操作全部都移動到 TypeScript 之中,畢竟很多時候比起寫樣板上的語法,寫程式才是我們最擅長的啊!
今天的範例程式碼參考:
https://stackblitz.com/edit/itironman-2019-exportas
拜讀文章時,有看到template出現appColorful="blue"
的地方,那我是不是也要在ColorfulDirective
的appColorful
掛上@Input()
這樣才接的到值(blue)呢?
另外
在宣告 @Directive() 裝飾我們的 directive 時,裡面可以設定一個
exportAs: []
,用來代表這個 directive 實體要以什麼名稱分享出去
我覺得改為 exportAs: string
會比較清楚
感謝您
Hi,謝謝您的提醒,原來的程式碼確實有些錯誤的地方,已經修正囉!